import os, sys
import re
import sqlite3
from datetime import datetime, timedelta
import json
from pathlib import Path
import logging

from openai import OpenAI
from dotenv import load_dotenv
from langchain_deepseek import ChatDeepSeek
from mcp.server.fastmcp import FastMCP
import random
from datetime import datetime
import pymysql
import numpy as np
import pandas as pd
from typing import List, Dict, Any

# 数据读取模式，例如 SQLITE, MYSQL等
data_connection_mode = "SQLITE"

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# 构建文件读写路径
file_folder_path = Path(__file__).resolve().parent.parent / "files"

load_dotenv()

# MySQL连接配置
MYSQL_CONFIG = {
    'host': os.getenv('MYSQL_HOST', 'localhost'),
    'port': int(os.getenv('MYSQL_PORT', 3306)),
    'user': os.getenv('MYSQL_USER', 'root'),
    'password': os.getenv('MYSQL_PASSWORD', ''),
    'database': os.getenv('MYSQL_DATABASE', 'user_insight'),
    'charset': 'utf8mb4',
    'cursorclass': pymysql.cursors.DictCursor  # 使结果自动转为字典格式
}

async def deepseek_response(query: str):
    client = OpenAI(api_key=os.getenv("ALIYUN_KEY"), base_url=os.getenv("ALI_BASE_URL"))

    response = client.chat.completions.create(
        model=os.getenv("ALIYUN_MODEL"),
        temperature = 0,
        messages=[
            {"role": "system", "content": "You are a helpful assistant"},
            {"role": "user", "content": query},
        ],
        stream=False
    )
    return response.choices[0].message.content

def get_current_time():
    # 获取当前日期和时间
    current_datetime = datetime.now()
    # 格式化当前日期和时间
    formatted_time = current_datetime.strftime('%Y-%m-%d %H:%M:%S')
    # 返回格式化后的当前时间
    return f"当前时间：{formatted_time}。"

async def query_processing(query: str):
    current_time = get_current_time()
    background_template = """
    输入：query 字符串类型 内容为用户关于时序模式的相关问题
    
    共有3中类型的query：
    类型一：请帮我分析【时间段】的【指标】的变化情况，并预测【时间段】的数据
    类型二：请帮我分析【时间段】的【指标】的变化情况
    类型三：请分析【时间点】【指标1】和【指标2】的流转关系
    【时间段】是指形如"YYYYMMDD到YYYYMMDD""YYYYMMDD-YYYYMMDD""近n天"等表示一段时间的短语
    【指标】是指形如"家宽流量""用户人数""家庭圈数量"等可统计的指标
    【时间点】是指形如"YYYYMMDD"等表示日期的短语

    输出: 内容为问题的关键参数,json格式
    user_scope 字符串类型 内容为用户范围
    start_time 字符串类型 内容为开始时间 请以YYYYMMDD的形式返回
    end_time 字符串类型 内容为结束时间 请以YYYYMMDD的形式返回
    index 字符串类型 内容为指标名称
    query_type 整数类型 内容为上述query类型，取值为1，2，3
    pred_day_length 整数类型 表示需要预测的天数，如果query_type为2或3，则取0

    样例:
    输入: 请帮我查询近1个月的家宽用户数量
    query: 请帮我查询近1个月的家宽用户数量
    user_scope: 所有家宽用户
    start_time: 20250725
    end_time: 20250825
    index: 用户数量
    query_type：2
    pred_day_length：0

    需要重点注意的细节:
    1. 请注意如果start_time和end_time都没有显式指定，那么end_time为大模型的当前时间，请根据用户query推理出start_time，如果无法推理出start_time，那么设定start_time为end_time前30天
    2. 如果用户显式指定当前时间，例如用户说“设定现在的时间是YYYYMMDD”，那么则根据语义设定start_time或end_time为YYYYMMDD
    3. 第2点举例，例如用户指定“假设现在的时间是20250314，请帮我分析北京当前时间40天后的家宽用户数量情况”，则start_time为20250314
    4. 第2点举例，例如用户指定“设定现在的时间是20250714，请帮我分析北京家宽用户近一个月的家宽流量使用情况”，则end_time为20250714
    5. 请确保start_time在end_time之前，并且都不为空
    """
    time_correction_template = f"当前的用户query是{query}，当前的背景是{background_template}，当前时间是{current_time}, 请根据query提取关键参数，并根据返回结构中的问题本身和开始时间结束时间，来决定开始时间结束时间是否要校正，请注意，如果用户没有特别指定时间，那么则更改结束时间为当前时间，相应的开始时间也要更改，结果以同样格式的json返回，注意只返回json，不要有其他任何东西"
    response = await deepseek_response(time_correction_template)
    cleaned_text =response.strip()
    cleaned_text = re.sub(r'^```json\s*', '', cleaned_text)  # 移除开头的 ```json
    cleaned_text = re.sub(r'\s*```$', '', cleaned_text)  # 移除结尾的 ```
    cleaned_text = cleaned_text.strip()
    return cleaned_text

async def text_to_sql(sql_query: str, query_key_parameter: dict) -> str:
    """
    本工具的功能是把用户的自然语言表述的需求转化为数据库查询语句sql
    输入为用户query,和query关键参数query_key_parameter
    输入为sql语句，字符串形式
    请识别用户的查询需求和预测需求，只需要把查询需求转换为sql，下面是举例
    1.
    query是 请帮我分析北京市2025年7月3日到2025年7月16日每日总家宽条数的变化，并预测之后一周的数据并用图表展示
    那么查询需求是 请帮我分析北京市2025年7月3日到2025年7月16日每日总家宽条数的变化
    预测需求是 预测之后一周的数据并用图表展示
    """

    schema_description = """
    - 有且仅有一个table: user_data

    - user_data中的字段含义，第一行为表头，然后每一行是字段的详细信息：
    字段中文名	字段英文名	字段类型	样例	注释
    统计时间	STATIS_TIME	字符串	20250731	
    省份	PROV	字符串	北京、山东、云南	
    自然人编码	PERSON_ID	字符串	P00001-P99999	
    手机号码	PHONE_NUM	字符串	13900000000	
    用户编码	USER_ID	字符串	U00001-U99999	
    客户编码	CUST_ID	字符串	C00001-C99999	
    属性-年龄	ATTR_AGE	整数	29	
    属性-性别	ATTR_GENDER	字符串	男、女	
    属性-省	ATTR_PROVINCE	字符串	北京市	
    属性-市	ATTR_CITY	字符串	海淀区	
    属性-区县街道	ATTR_COUNTY	字符串	中关村街道	
    属性-所属网格编码	ATTR_GRID_ID	字符串	G001-G999	
    属性-所属网格经度	ATTR_GRID_LAT	浮点数	39.43-41.05	
    属性-所属网格纬度	ATTR_GRID_LONG	浮点数	115.25-117.30	
    属性-日间常驻基站编码	ATTR_DAY_BASE_ID	字符串	B0001-B9999	
    属性-日间常驻基站经度	ATTR_DAY_BASE_LAT	浮点数	39.43-41.05	
    属性-日间常驻基站纬度	ATTR_DAY_BASE_LONG	浮点数	115.25-117.30	
    属性-夜间常驻基站编码	ATTR_NIGHT_BASE_ID	字符串	B0001-B9999	
    属性-夜间常驻基站经度	ATTR_NIGHT_BASE_LAT	浮点数	39.43-41.05	
    属性-夜间常驻基站纬度	ATTR_NIGHT_BASE_LONG	浮点数	115.25-117.30	
    属性-资费波动标签	ATTR_FEE_FLUCTUATION	字符串	稳定型、波动型	
    标签-是否全球通用户	LABEL_IS_GSM	整数	0、1	
    标签-是否腰部用户	LABEL_IS_MID	整数	0、1	
    标签-是否中高端用户	LABEL_IS_PREM	整数	0、1	
    标签-是否异网用户	LABEL_IS_DIFF	整数	0、1	若为异网用户，则用户编码、客户编码、年龄、所属网格、是否全球通/腰部/中高端/银发/用户、指标、使用流量、通话时长置空
    标签-是否银发用户	LABEL_IS_ELDER	整数	0、1	
    标签-是否国际用户	LABEL_IS_INT	整数	0、1	
    属性-家庭圈ID	ATTR_FAMILY_CIRCLE_ID	字符串	FC001-FC999	与家人之间的关系形成的圈
    属性-地理圈ID	ATTR_GEO_CIRCLE_ID	字符串	GEO001-GEO999	日间地理圈
    属性-交往圈ID	ATTR_SOCIAL_CIRCLE_ID	字符串	SC001-SC999	与朋友、同事等社交关系形成的圈
    属性-社区圈ID	ATTR_COMMUNITY_CIRCLE_ID	字符串	CYC001-CYC999	与居住地的邻居关系形成的圈
    属性-校园圈ID	ATTR_CAMPUS_CIRCLE_ID	字符串	CPC001-CPC999	在学校中与同学、老师和的关系形成的圈
    属性-通勤圈ID	ATTR_COMMUTE_CIRCLE_ID	字符串	CTC001-CTC999	日常上下班/学的路线和相关地点形成的圈
    属性-商业圈ID	ATTR_BUSINESS_CIRCLE_ID	字符串	BC001-BC999	在商圈附近活动形成的圈
    属性-网络圈ID	ATTR_ONLINE_CIRCLE_ID	字符串	OC001-OC999	互联网建立的虚拟社交网络形成的圈
    指标-ARPU（元）	METRIC_ARPU	浮点数	139.9	过去12个月的平均月资费
    指标-使用流量（MB）	METRIC_FLUX_USED	浮点数	2097152.66	
    指标_家宽条数	METRIC_BROADBAND_COUNT	整数	2	单位：条
    指标_家宽平均流量（MB）	METRIC_BROADBAND_TRAFFIC	浮点数	203002	单位：MB  自然人宽带流量均值
    指标_家宽在线时长（分钟）	METRIC_BROADBAND_ONLINETIME	浮点数	68400	单位：分钟  自然人在线时长均值
    指标-月消费金额(元)	METRIC_MONTHLY_CONSUMPTION	浮点型	128.50、256.80	
    指标-基本套餐费(元）	METRIC_BASIC_PACKAGE_FEE	浮点型	58.00、88.00	
    指标-本地通话费（元）	METRIC_LOCAL_CALL_FEE	浮点型	15.30、28.50	
    指标-异地通话费（元）	METRIC_LONG_DISTANCE_CALL_FEE	浮点型	8.20、0.00	
    指标-国内流量包业务费（元）	METRIC_DOMESTIC_DATA_FEE	浮点型	25.00、45.00	
    指标-国际流量包业务费（元）	METRIC_INTERNATIONAL_DATA_FEE	浮点型	0.00、15.60	
    指标-附加业务费（元）	METRIC_ADDITIONAL_SERVICE_FEE	浮点型	12.00、20.00	
    指标-增值业务费（元）	METRIC_VALUE_ADDED_SERVICE_FEE	浮点型	10.00、18.30	
    指标-流量使用费（元）	METRIC_DATA_USAGE_FEE	浮点型	5.2、12.8	
    指标-通话时长（分钟）	METRIC_CALL_DURATION_MINUTES	整型	2、3	
    属性-激活日期	ATTR_NUMBER_ACTIVATION_DATE	时间	20180317	
    属性-套餐名称	ATTR_NUMBER_PLAN_NAME	字符串	移动：移动大王卡，全球通尊享套餐，神州行畅聊卡，5G智享套餐，和飞享套餐
    电信：电信无忧卡，十全十美套餐，天翼畅享套餐，5G融合套餐，青年一派卡
    联通：联通大王卡，冰淇淋套餐，沃派套餐，G畅爽套餐，校园套餐

    属性-离网日期	ATTR_NUMBER_TERMINATION_DATE	时间	20180317	
    属性-入网地址	ATTR_NUMBER_REGISTERED_LOCATION	字符串	北京市房山区	
    指标-套餐总流量（MB）	METRIC_NUMBER_DATA_ALLOWANCE_MB	整型	20	
    指标-套餐总通话时长（分钟）	METRIC_NUMBER_FREE_CALL_MINUTES	整型	200	
    标签-是否携号转网	LABEL_NUMBER_PORTABILITY_STATUS	字符串	否	
    指标-白天常驻基站时间（分钟）	METRIC_LAC_DAY_TOTAL_TIME_MINUTES	整型	8659	
    指标-夜间常驻基站时间（分钟）	METRIC_LAC_NIGHT_TOTAL_TIME_MINUTES	整型	4148	
    指标-白天常驻基站连接次数	METRIC_LAC_DAY_LAC_COUNT	整型	12	
    指标-夜间常驻基站连接次数	METRIC_LAC_NIGHT_LAC_COUNT	整型	3	
    属性-家宽地址	ATTR_BRD_ADDR	字符串	朝阳区望京新城15栋061	
    属性-使用产品名称	ATTR_PRODUCT_NAME	字符串	移动看家, 移动高清, 移动康养, 移动车家, 移动云盘,139邮箱, 云手机, 云电脑, 行车卫士, 集团语音	
    属性-所属集团名称	ATTR_GROUP_NAME	字符串	阿里巴巴集团,腾讯控股集团,中国工商银行集团,国家电网公司,华为技术有限公司,中国石油集团,中国建筑集团,顺丰控股集团,新东方教育集团,华润医药集团

    属性-信用分总得分	ATTR_CREDIT_SCORE	整型	420-696（北京市4月的真实数据）均值 550	与通话、漫游、教育/金融/旅行/汽车/购物/餐饮app、arpu、宽带、
    属性-到达地址时间	ATTR_ARRIVE_TIME	时间	20240115 06:00:00	
    属性-到达地址	ATTR_ARRIVE_ADDR	字符串	海淀区紫金长安3栋052	
    属性-到达地址类型	ATTR_ARRIVE_ADD_TYPE	字符串	住宅、公司、餐厅、超市、景点、影院、4S店、小学、中学、大学	
    属性-到达地址经度	ATTR_ARRIVE_LONGITUDE	浮点型	116.3108	
    属性-到达地址纬度	ATTR_ARRIVE_LATITUDE	浮点型	39.9072	
    指标-到达地址停留时间（分钟）	METRIC_ARRIVE_STAY_TIME	整型	607	
    属性-到达地址基站	ATTR_ARRIVE_LAC_CODE	字符串	L00014	
    指标-通话时间	METRIC_CALL_TIMESTAMP	时间	20240101 11:48:00	
    属性-通话对端号码	ATTR_CALL_NUMBER	字符串	18517699330	
    属性-通话漫游类型	ATTR_CALL_ROAMING_TYPE	字符串	国内、国际	
    指标-通话时长（分钟）	METRIC_CALL_DURATION_MINUTES	浮点型	2.4	
    指标-通话费用	METRIC_CALL_APP_COST_RMB	浮点型	0	
    指标-APP使用时间	METRIC_TIMESTAMP	时间	2024/1/1 7:00	
    指标-APP上行流量（MB）	METRIC_APP_UPLOAD_TRAFFIC_MB	浮点型	1.34	
    指标-APP下行流量（MB）	METRIC_APP_DOWNLOAD_TRAFFIC_MB	浮点型	2.67	
    指标-APP在线时长（分钟）	METRIC_APP_ONLINE_DURATION_MINUTES	整型	14	
    属性-APP名称	ATTR_APP_USED	字符串	微信、抖音、支付宝、淘宝、拼多多、高德地图、QQ、快手、百度、京东、小红书、微博、美团、滴滴出行、哔哩哔哩、饿了么、WPS Office、腾讯视频、爱奇艺、携程、夸克、百度网盘、Soul、知乎、百度地图、网易新闻、虎牙直播、番茄小说、剪映、懂车帝	
    指标-APP花费（RMB）	ATTR_APP_COST_RMB	浮点型	0	
    属性-集团产品名称	ATTR_GROUP_PRODUCT_NAME	字符串	移动看家, 移动高清, 移动康养, 移动车家, 移动云盘,139邮箱, 云手机, 云电脑, 行车卫士, 集团语音	                
    """

    demo_template = """
    1. 北京市近一周每日总家宽条数的变化
    ```sql

    SELECT 
        STATIS_TIME AS '日期',
        SUM(ATTR_BROADBAND_COUNT) AS '总家宽条数'
    FROM 
        user_data
    WHERE 
        PROV_ID = '北京'
        AND STATIS_TIME BETWEEN '20250725' AND '20250731'
        AND ATTR_BROADBAND_COUNT IS NOT NULL
    GROUP BY 
        STATIS_TIME
    ORDER BY 
        STATIS_TIME ASC;
    ```

    2、北京市2025年1月每日移动套餐用户数变化
    ```sql
    SELECT 
        STATIS_TIME AS '日期',
        COUNT(DISTINCT USER_ID) AS '移动套餐用户数'
    FROM 
        user_data
    WHERE 
        PROV = '北京'
        AND STATIS_TIME BETWEEN '20250101' AND '20250131'
        AND LABEL_IS_DIFF = 0
    GROUP BY 
        STATIS_TIME
    ORDER BY 
        STATIS_TIME ASC;
    ```

    3、北京市2025年1月家庭宽带用户日均在线时长
    ```sql
    SELECT 
    STATIS_TIME AS '日期',
        AVG(METRIC_BROADBAND_ONLINETIME) AS '日均在线时长'
    FROM 
        user_data
    WHERE 
        PROV = '北京'
        AND ATTR_BROADBAND_COUNT > 0
        AND STATIS_TIME BETWEEN '20250101' AND '20250131'
    GROUP BY 
        STATIS_TIME
    ORDER BY 
        STATIS_TIME ASC;
    ```
    """

    data_sample = """
    20250101|北京|P39752|13697491495|U22808|C77213|72|女|北京市|密云区|不老屯镇|G00130|39.5|115.94|B06647|40.12|116.63|B00067|39.8|116.69|稳定型|0|0|0|0|0|1|FC00614|GEO00169|SC00885|CYC00362|CPC00886|CTC00470|BC00283|OC00534|274.117615.0|50950.0|201.57|71.66|49.4|16.5|72.02|32.27|1.42|15.61|18.58|48|20190511|和飞享套餐|82|311||陕西省丽娟县魏都向街t座 471483|否|856|615|19|9|河北省邯郸市友好澳门路O座 922221|139邮箱|国家电网公司|579|20240512 04:42:30|广西壮9|127|L00320|20240304 20:20:56|15213535211|国际|16.8|6.46|20240518 11:07:19|7.59|7.6|8|京东|12.24|移动高清
    20250101|北京|P71379|13944849915|U20652|C19173|35|女|北京市|石景山区|广宁街道|G00627|40.87|115.63|B02638|40.14|115.42|B02740|40.24|116.56|波动型|0|1|0|0|1|0|FC00927|GEO00404|SC00965|CYC00235|CPC00145|CTC00799|BC00507|OC00900|4462114.0|63776.0|217.85|43.24|4.52|21.84|68.18|45.18|2.15|39.0|15.33|37|20160121|天翼畅享套餐|9|574||上海市齐齐哈尔县花溪淮安路K座 165899|是|536|517|12|9|上海市北京市秀英孙街C座 181545|云手机|顺丰控股集团|580|20240903 18:02:51|江8|L09841|20241128 04:10:03|13464530864|国内|42.6|6.67|20240901 08:30:04|9.31|29.83|71|爱奇艺|19.85|行车卫士
    20250101|北京|P38664|15612803181|U09527|C89431|43|男|北京市|怀柔区|九渡河镇|G00987|40.43|115.81|B00973|40.83|116.21|B00374|40.93|115.89|稳定型|0|0|0|1|0|1|FC00999|GEO00186|SC00887|CYC00857|CPC00881|CTC00633|BC00337|OC00914|196.62322.0|85831.0|142.28|62.58|39.06|18.59|18.25|33.87|23.94|44.71|13.16|149|20110105|冰淇淋套餐|86|663||辽宁省瑞市怀柔秦路X座 719788|是|606|307|7|2|湖北省东莞县锡山兴安盟路z座 448679|集团语音|顺丰控股集团|665|20240320 02:04:09|甘|574|L07281|20240619 07:35:00|13955600909|国内|49.9|1.46|20241122 17:35:20|5.32|31.76|37|番茄小说|27.21|移动云盘
    """

    sql_generation_template = f"""
    你是一位资深数据库分析师，精通SQL语法和语义解析，擅长将自然语言查询转化为高效准确的SQL语句。请严格遵守数据库安全规范，仅生成SELECT查询语句。 
    请参考用户以自然语言描述的查询需求，问题关键变量，数据库结构，样例数据和生成sql的样例，将用户的sql_query转化为可执行的sql语句。

    用户以自然语言描述的查询需求是:
    {sql_query}

    问题关键变量:
    {query_key_parameter}

    数据库结构：
    {schema_description}

    样例数据:
    {data_sample}

    生成sql的样例：
    {demo_template}

    要求：
    1. 只输出SQL语句，直接输出字符串，不要包含解释，语句前后不要包含"```sql""```"
    2. 使用标准SQL语法
    3. 确保查询效率
    4. 请预先测试，不要有语法错误
    5. 不要直接使用demo_template中的具体数据，demo_template只是用来参考学习sql的生成格式和生成逻辑
    """

    response = await deepseek_response(sql_generation_template)

    return response

async def sql_inquire(sql_query: str) -> str:
    """
    当用户需要进行数据库查询工作时，请调用该函数。
    该函数用于在指定SQLite数据库上运行一段SQL代码，完成数据查询相关工作，
    并且当前函数是使用Python标准库sqlite3连接SQLite数据库。
    本函数只负责运行SQL代码并进行数据查询。
    :param sql_query: 字符串形式的SQL查询语句，用于执行对SQLite数据库进行查询，并获得相关信息
    :return：sql_query在SQLite中的运行结果。
    """
    if data_connection_mode == "SQLITE":
        # 创建连接（SQLite使用文件路径，若文件不存在会自动创建）
        connection = sqlite3.connect(file_folder_path / 'sqlite_data.db')
        # 设置行工厂，让结果包含字段名
        connection.row_factory = sqlite3.Row

        try:
            cursor = connection.cursor()
            cursor.execute(sql_query)
            # 获取所有查询结果
            results = cursor.fetchall()
            # 转换为包含字段名的字典列表
            formatted_results = [dict(row) for row in results]
        except Exception as e:
            # 捕获并返回错误信息
            return json.dumps({"error": str(e)}, ensure_ascii=False)
        finally:
            connection.close()

        # 将结果以JSON字符串形式返回
        return json.dumps(formatted_results, ensure_ascii=False)

    elif data_connection_mode == "MYSQL":
        connection = None
        try:
            # 创建MySQL连接
            connection = pymysql.connect(**MYSQL_CONFIG)

            with connection.cursor() as cursor:
                cursor.execute(sql_query)

                # 获取查询结果
                if sql_query.strip().upper().startswith('SELECT'):
                    results = cursor.fetchall()
                    return json.dumps(results, ensure_ascii=False)
                else:
                    # 对于非SELECT语句，返回影响的行数
                    connection.commit()
                    return json.dumps({"affected_rows": cursor.rowcount}, ensure_ascii=False)

        except Exception as e:
            # 回滚事务并返回错误信息
            if connection:
                connection.rollback()
            return json.dumps({"error": str(e)}, ensure_ascii=False)
        finally:
            if connection:
                connection.close()
    else:
        raise Exception("Unsupported Data Connection Mode.")

async def visualize_line_chart(data, predict_data, query_key_parameter: str) -> str:
    try:
        data_json = json.dumps(data)
        predict_json = json.dumps(predict_data)
        query_key_parameter = json.loads(query_key_parameter)

        caliber = "用户群体：" + query_key_parameter["user_scope"]
        caliber += " 统计指标：" + query_key_parameter["index"]
        caliber += " 开始时间：" + query_key_parameter["start_time"]
        caliber += " 结束时间：" + query_key_parameter["end_time"]

        data_points = json.loads(data_json)
        predict_points = json.loads(predict_json)
    except Exception as e:
        print("error")
        return f"输入数据格式错误: {e}"

    # 构造x轴和y轴数据
    x_data = [point["time"] for point in data_points] + [point["time"] for point in predict_points if
                                                         point["time"] not in [p["time"] for p in data_points]]

    # 构造已有数据系列
    historical_data = [point["value"] for point in data_points]

    # 构造预测数据系列，从历史数据的最后一个点开始，前面用None填充
    predict_data = [None] * (len(data_points) - 1) + [historical_data[-1]]
    if data_points and predict_points:
        # 从历史数据的最后一个点开始
        for point in predict_points:
            predict_data.append(point["value"])

    # 生成ECharts配置
    echarts_config = {
        "title": {
            "text": f"统计口径 - {caliber}",
            "left": "center",
            "textStyle": {
                "fontSize": 18,
                "fontWeight": "bold"
            }
        },
        "tooltip": {
            "trigger": "axis",
            "axisPointer": {
                "type": "cross"
            }
        },
        "legend": {
            "data": ["已有数据", "预测数据"],
            "top": 30
        },
        "grid": {
            "left": "3%",
            "right": "4%",
            "bottom": "15%",
            "containLabel": True
        },
        "dataZoom": [
            {
                "type": "slider",
                "show": True,
                "xAxisIndex": [0],
                "start": 0,
                "end": 100,
                "bottom": "5%",
                "height": "5%",
                "borderColor": "transparent",
                "backgroundColor": "#f7f7f7",
                "fillerColor": "rgba(167,183,204,0.4)",
                "handleStyle": {
                    "color": "#5470C6",
                    "borderColor": "#5470C6"
                },
                "textStyle": {
                    "color": "#333"
                }
            },
            {
                "type": "inside",
                "xAxisIndex": [0],
                "start": 0,
                "end": 100
            }
        ],
        "xAxis": {
            "type": "category",
            "data": x_data,
            "name": "时序"
        },
        "dataZoom": [
            {
                "type": "slider",
                "show": True,
                "xAxisIndex": [0],
                "start": 0,
                "end": 100,
                "bottom": "5%",
                "height": "5%",
                "borderColor": "transparent",
                "backgroundColor": "#f7f7f7",
                "fillerColor": "rgba(167,183,204,0.4)",
                "handleStyle": {
                    "color": "#5470C6",
                    "borderColor": "#5470C6"
                },
                "textStyle": {
                    "color": "#333"
                }
            },
            {
                "type": "inside",
                "xAxisIndex": [0],
                "start": 0,
                "end": 100
            }
        ],
        "yAxis": {
            "type": "value",
            "name": "数值"
        },
        "series": [
            {
                "name": "预测数据",
                "type": "line",
                "data": predict_data,
                "itemStyle": {
                    "color": "#EE6666"
                },
                "lineStyle": {
                    "color": "#EE6666",
                    "type": "dashed",
                    "width": 3
                },
                "symbol": "diamond",
                "symbolSize": 8
            },
            {
                "name": "已有数据",
                "type": "line",
                "data": historical_data,
                "itemStyle": {
                    "color": "#5470C6"
                },
                "lineStyle": {
                    "color": "#5470C6",
                    "width": 3
                },
                "symbol": "circle",
                "symbolSize": 6
            }
        ]
    }

    echarts_config_for_frontend = {
        "title": {
            "text": f"统计口径 - {caliber}",
            "left": "center",
            "textStyle": {
                "fontSize": 12,
                "fontWeight": "bold"
            }
        },
        "tooltip": {
            "trigger": "axis",
            "axisPointer": {
                "type": "cross"
            }
        },
        "legend": {
            "data": ["已有数据", "预测数据"],
            "top": 30
        },
        "grid": {
            "left": "3%",
            "right": "6%",
            "bottom": "12%",
            "containLabel": True
        },
        "dataZoom": [
            {
                "type": "slider",
                "show": True,
                "xAxisIndex": [0],
                "start": 0,
                "end": 100,
                "bottom": "5%",
                "height": "5%",
                "borderColor": "transparent",
                "backgroundColor": "#f7f7f7",
                "fillerColor": "rgba(167,183,204,0.4)",
                "handleStyle": {
                    "color": "#5470C6",
                    "borderColor": "#5470C6"
                },
                "textStyle": {
                    "color": "#333"
                }
            },
            {
                "type": "inside",
                "xAxisIndex": [0],
                "start": 0,
                "end": 100
            }
        ],
        "xAxis": {
            "type": "category",
            "data": x_data,
            "name": "时序"
        },
        "dataZoom": [
            {
                "type": "slider",
                "show": True,
                "xAxisIndex": [0],
                "start": 0,
                "end": 100,
                "bottom": "5%",
                "height": "5%",
                "borderColor": "transparent",
                "backgroundColor": "#f7f7f7",
                "fillerColor": "rgba(167,183,204,0.4)",
                "handleStyle": {
                    "color": "#5470C6",
                    "borderColor": "#5470C6"
                },
                "textStyle": {
                    "color": "#333"
                }
            },
            {
                "type": "inside",
                "xAxisIndex": [0],
                "start": 0,
                "end": 100
            }
        ],
        "yAxis": {
            "type": "value",
            "name": "数值"
        },
        "series": [
            {
                "name": "预测数据",
                "type": "line",
                "data": predict_data,
                "itemStyle": {
                    "color": "#EE6666"
                },
                "lineStyle": {
                    "color": "#EE6666",
                    "type": "dashed",
                    "width": 3
                },
                "symbol": "diamond",
                "symbolSize": 8
            },
            {
                "name": "已有数据",
                "type": "line",
                "data": historical_data,
                "itemStyle": {
                    "color": "#5470C6"
                },
                "lineStyle": {
                    "color": "#5470C6",
                    "width": 3
                },
                "symbol": "circle",
                "symbolSize": 6
            }
        ]
    }

    echarts_config = json.dumps(echarts_config, ensure_ascii=False)
    echarts_config_for_frontend = json.dumps(echarts_config_for_frontend, ensure_ascii=False)

    html_content = f"""
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>时序模式可视化图表</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.3/echarts.min.js"></script>
    <style>
        body {{
            font-family: Arial, sans-serif;
            margin: 20px;
            background-color: #f5f5f5;
        }}
        .container {{
            max-width: 1200px;
            margin: 0 auto;
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }}
        h1 {{
            color: #333;
            text-align: center;
            margin-bottom: 30px;
        }}
        #chart {{
            width: 100%;
            height: 500px;
        }}
        .loading {{
            text-align: center;
            padding: 50px;
            color: #666;
        }}
    </style>
    </head>
    <body>
    <div class="container">
        <h1>时间序列数据可视化</h1>
        <div id="chart">
            <div class="loading">正在加载图表...</div>
        </div>
    </div>

    <script>
        // 等待页面加载完成
        document.addEventListener('DOMContentLoaded', function() {{
            // 检查ECharts是否加载成功
            if (typeof echarts === 'undefined') {{
                document.getElementById('chart').innerHTML = '<div class="loading">ECharts库加载失败，请检查网络连接</div>';
                return;
            }}

            try {{
                // 初始化图表
                var chartDom = document.getElementById('chart');
                var myChart = echarts.init(chartDom);

                var option = {echarts_config};

                myChart.setOption(option);

                // 响应式调整
                window.addEventListener('resize', function() {{
                    myChart.resize();
                }});

                console.log('图表加载成功');
            }} catch (error) {{
                console.error('图表初始化失败:', error);
                document.getElementById('chart').innerHTML = '<div class="loading">图表初始化失败: ' + error.message + '</div>';
            }}
        }});
    </script>
    </body>
    </html>
            """

    # 保存HTML到文件
    import os
    output_file = "time_series_chart.html"
    output_file_2 = "chart_option.json"
    with open(file_folder_path / output_file, "w", encoding="utf-8") as f:
        f.write(html_content)

    with open(file_folder_path / output_file_2, "w", encoding="utf-8") as f:
        f.write(echarts_config_for_frontend)

    print(f"HTML文件已保存到: {file_folder_path / output_file}")
    print("请在浏览器中打开该文件查看图表")

    return echarts_config_for_frontend


async def summary_factory(query, data, pred_data, query_key_parameter):
    answer_prompt = f"""
    请扮演一个问题回答和总结撰写的角色，基于以下文本，生成一份关于用户问题的回答，大约200字左右。

    【背景信息】：
    - 用户的原始问题是 {query}

    【待总结的文本】：
    - 时序智能体返回的关键参数是 {query_key_parameter}
    - 时序智能体返回的图表数据是 {data} 
    - 时序智能体返回的图表预测数据是 {pred_data}

    【具体要求】：
    1.  **重点突出**：请根据用户问题和智能体返回的文本和数据，进行有洞见的分析，抓住核心论点、结论和关键证据。
    2.  **结构清晰**：采用[如：总分总、列表、段落]的形式组织内容。
    3.  **长度控制**：将总结控制在[如：300字 / 4个要点]以内。
    4.  **语言风格**：使用[如：简洁、专业、口语化]的语言。
    5.  **输出格式**：请直接输出总结内容，以MD格式输出，不要添加额外解释。
    """

    summary_prompt = f"""
        请扮演一个数据分析和总结撰写的角色，基于以下文本，生成一份关于用户问题和返回数据的分析报告。

        【背景信息】：
        - 用户的原始问题是 {query}

        【待总结的文本】：
        - 时序智能体返回的关键参数是 {query_key_parameter}
        - 时序智能体返回的图表数据是 {data}
        - 时序智能体返回的图表预测数据是 {pred_data}

        【具体要求】：
        1.  **重点突出**：请根据用户问题和智能体返回的文本和数据，进行有洞见的分析，抓住核心论点、结论和关键证据。
        2.  **结构清晰**：采用[如：总分总、列表、段落]的形式组织内容。
        3.  **长度控制**：将总结控制在[如：300字 / 4个要点]以内。
        4.  **语言风格**：使用[如：简洁、专业、口语化]的语言。
        5.  **输出格式**：请直接输出总结内容，以MD格式输出，不要添加额外解释。
        """

    answer = await deepseek_response(answer_prompt)
    summary = await deepseek_response(summary_prompt)

    return [answer, summary]

async def predict_future(
    historical_data: List[Dict[str, Any]],
    periods: int = 5,
    min_value: float = 0.0,
    stability_threshold: float = 0.01
):
    """
    当用户需要进行数据预测时，请调用该函数。
    该函数接收包含多个日期的时间序列数据，输出指定天数的预测数据。
    参数:
        historical_data: 原始历史数据
        periods: 预测天数
        min_value: 最小值保护
        stability_threshold: 视为"不变"的变化量阈值
    返回:
        预测数据，list
    """
    if not historical_data or periods <= 0:
        return []
    try:
        # 数据预处理
        df = pd.DataFrame(historical_data)
        df['time'] = pd.to_datetime(df['time'], format='%Y%m%d')
        df = df.sort_values('time')

        # 线性回归拟合
        x = np.arange(len(df))
        y = df['value'].values
        a, b = np.polyfit(x, y, deg=1)
        fitted_values = a * x + b

        # 计算每个点与拟合线的关系
        deviations = y - fitted_values
        df['deviation'] = deviations

        # 计算三态频率和幅度
        up_deltas = []
        down_deltas = []

        for dev in deviations:
            if dev > stability_threshold:
                up_deltas.append(dev)
            elif dev < -stability_threshold:
                down_deltas.append(-dev)  # 存储绝对值

        # 计算三态概率
        total = len(deviations)
        up_prob = len(up_deltas) / total
        down_prob = len(down_deltas) / total
        stable_prob = 1 - up_prob - down_prob

        # 计算平均波动幅度
        avg_up = np.mean(up_deltas) if up_deltas else 0
        avg_down = np.mean(down_deltas) if down_deltas else 0

        # 生成预测
        last_date = df['time'].iloc[-1]
        predictions = []
        original_min = df['value'].min()

        random.seed(42)
        for i in range(1, periods + 1):
            # 基础线性预测
            next_x = len(df) + i - 1
            base_value = a * next_x + b

            # 添加波动
            rand = random.random()
            if rand < up_prob:
                final_value = base_value + avg_up + random.uniform(-avg_up / 2, avg_up / 2)
            elif rand < up_prob + down_prob:
                final_value = base_value - avg_down + random.uniform(-avg_down / 2, avg_down / 2)
            else:
                final_value = base_value


            # 记录预测
            next_date = last_date + timedelta(days=i)
            predictions.append({
                "time": next_date.strftime('%Y%m%d'),
                "value": max(final_value, 0)
            })
        return predictions
    except Exception as e:
        logger.error(f"预测失败: {str(e)}", exc_info=True)
        return {"error": f"预测失败: {str(e)}"}

async def sankey_to_sql(sql_query: str) -> str:

    schema_description = """
    - 有且仅有一个table: user_data

    - user_data中的字段含义，第一行为表头，然后每一行是字段的详细信息：
    字段中文名	字段英文名	字段类型	样例	注释
    统计时间	STATIS_TIME	字符串	20250731	
    省份	PROV	字符串	北京、山东、云南	
    自然人编码	PERSON_ID	字符串	P00001-P99999	
    手机号码	PHONE_NUM	字符串	13900000000	
    用户编码	USER_ID	字符串	U00001-U99999	
    客户编码	CUST_ID	字符串	C00001-C99999	
    属性-年龄	ATTR_AGE	整数	29	
    属性-性别	ATTR_GENDER	字符串	男、女	
    属性-省	ATTR_PROVINCE	字符串	北京市	
    属性-市	ATTR_CITY	字符串	海淀区	
    属性-区县街道	ATTR_COUNTY	字符串	中关村街道	
    属性-所属网格编码	ATTR_GRID_ID	字符串	G001-G999	
    属性-所属网格经度	ATTR_GRID_LAT	浮点数	39.43-41.05	
    属性-所属网格纬度	ATTR_GRID_LONG	浮点数	115.25-117.30	
    属性-日间常驻基站编码	ATTR_DAY_BASE_ID	字符串	B0001-B9999	
    属性-日间常驻基站经度	ATTR_DAY_BASE_LAT	浮点数	39.43-41.05	
    属性-日间常驻基站纬度	ATTR_DAY_BASE_LONG	浮点数	115.25-117.30	
    属性-夜间常驻基站编码	ATTR_NIGHT_BASE_ID	字符串	B0001-B9999	
    属性-夜间常驻基站经度	ATTR_NIGHT_BASE_LAT	浮点数	39.43-41.05	
    属性-夜间常驻基站纬度	ATTR_NIGHT_BASE_LONG	浮点数	115.25-117.30	
    属性-资费波动标签	ATTR_FEE_FLUCTUATION	字符串	稳定型、波动型	
    标签-是否全球通用户	LABEL_IS_GSM	整数	0、1	
    标签-是否腰部用户	LABEL_IS_MID	整数	0、1	
    标签-是否中高端用户	LABEL_IS_PREM	整数	0、1	
    标签-是否异网用户	LABEL_IS_DIFF	整数	0、1	若为异网用户，则用户编码、客户编码、年龄、所属网格、是否全球通/腰部/中高端/银发/用户、指标、使用流量、通话时长置空
    标签-是否银发用户	LABEL_IS_ELDER	整数	0、1	
    标签-是否国际用户	LABEL_IS_INT	整数	0、1	
    属性-家庭圈ID	ATTR_FAMILY_CIRCLE_ID	字符串	FC001-FC999	与家人之间的关系形成的圈
    属性-地理圈ID	ATTR_GEO_CIRCLE_ID	字符串	GEO001-GEO999	日间地理圈
    属性-交往圈ID	ATTR_SOCIAL_CIRCLE_ID	字符串	SC001-SC999	与朋友、同事等社交关系形成的圈
    属性-社区圈ID	ATTR_COMMUNITY_CIRCLE_ID	字符串	CYC001-CYC999	与居住地的邻居关系形成的圈
    属性-校园圈ID	ATTR_CAMPUS_CIRCLE_ID	字符串	CPC001-CPC999	在学校中与同学、老师和的关系形成的圈
    属性-通勤圈ID	ATTR_COMMUTE_CIRCLE_ID	字符串	CTC001-CTC999	日常上下班/学的路线和相关地点形成的圈
    属性-商业圈ID	ATTR_BUSINESS_CIRCLE_ID	字符串	BC001-BC999	在商圈附近活动形成的圈
    属性-网络圈ID	ATTR_ONLINE_CIRCLE_ID	字符串	OC001-OC999	互联网建立的虚拟社交网络形成的圈
    指标-ARPU（元）	METRIC_ARPU	浮点数	139.9	过去12个月的平均月资费
    指标-使用流量（MB）	METRIC_FLUX_USED	浮点数	2097152.66	
    指标_家宽条数	METRIC_BROADBAND_COUNT	整数	2	单位：条
    指标_家宽平均流量（MB）	METRIC_BROADBAND_TRAFFIC	浮点数	203002	单位：MB  自然人宽带流量均值
    指标_家宽在线时长（分钟）	METRIC_BROADBAND_ONLINETIME	浮点数	68400	单位：分钟  自然人在线时长均值
    指标-月消费金额(元)	METRIC_MONTHLY_CONSUMPTION	浮点型	128.50、256.80	
    指标-基本套餐费(元）	METRIC_BASIC_PACKAGE_FEE	浮点型	58.00、88.00	
    指标-本地通话费（元）	METRIC_LOCAL_CALL_FEE	浮点型	15.30、28.50	
    指标-异地通话费（元）	METRIC_LONG_DISTANCE_CALL_FEE	浮点型	8.20、0.00	
    指标-国内流量包业务费（元）	METRIC_DOMESTIC_DATA_FEE	浮点型	25.00、45.00	
    指标-国际流量包业务费（元）	METRIC_INTERNATIONAL_DATA_FEE	浮点型	0.00、15.60	
    指标-附加业务费（元）	METRIC_ADDITIONAL_SERVICE_FEE	浮点型	12.00、20.00	
    指标-增值业务费（元）	METRIC_VALUE_ADDED_SERVICE_FEE	浮点型	10.00、18.30	
    指标-流量使用费（元）	METRIC_DATA_USAGE_FEE	浮点型	5.2、12.8	
    指标-通话时长（分钟）	METRIC_CALL_DURATION_MINUTES	整型	2、3	
    属性-激活日期	ATTR_NUMBER_ACTIVATION_DATE	时间	20180317	
    属性-套餐名称	ATTR_NUMBER_PLAN_NAME	字符串	移动：移动大王卡，全球通尊享套餐，神州行畅聊卡，5G智享套餐，和飞享套餐
    电信：电信无忧卡，十全十美套餐，天翼畅享套餐，5G融合套餐，青年一派卡
    联通：联通大王卡，冰淇淋套餐，沃派套餐，G畅爽套餐，校园套餐

    属性-离网日期	ATTR_NUMBER_TERMINATION_DATE	时间	20180317	
    属性-入网地址	ATTR_NUMBER_REGISTERED_LOCATION	字符串	北京市房山区	
    指标-套餐总流量（MB）	METRIC_NUMBER_DATA_ALLOWANCE_MB	整型	20	
    指标-套餐总通话时长（分钟）	METRIC_NUMBER_FREE_CALL_MINUTES	整型	200	
    标签-是否携号转网	LABEL_NUMBER_PORTABILITY_STATUS	字符串	否	
    指标-白天常驻基站时间（分钟）	METRIC_LAC_DAY_TOTAL_TIME_MINUTES	整型	8659	
    指标-夜间常驻基站时间（分钟）	METRIC_LAC_NIGHT_TOTAL_TIME_MINUTES	整型	4148	
    指标-白天常驻基站连接次数	METRIC_LAC_DAY_LAC_COUNT	整型	12	
    指标-夜间常驻基站连接次数	METRIC_LAC_NIGHT_LAC_COUNT	整型	3	
    属性-家宽地址	ATTR_BRD_ADDR	字符串	朝阳区望京新城15栋061	
    属性-使用产品名称	ATTR_PRODUCT_NAME	字符串	移动看家, 移动高清, 移动康养, 移动车家, 移动云盘,139邮箱, 云手机, 云电脑, 行车卫士, 集团语音	
    属性-所属集团名称	ATTR_GROUP_NAME	字符串	阿里巴巴集团,腾讯控股集团,中国工商银行集团,国家电网公司,华为技术有限公司,中国石油集团,中国建筑集团,顺丰控股集团,新东方教育集团,华润医药集团

    属性-信用分总得分	ATTR_CREDIT_SCORE	整型	420-696（北京市4月的真实数据）均值 550	与通话、漫游、教育/金融/旅行/汽车/购物/餐饮app、arpu、宽带、
    属性-到达地址时间	ATTR_ARRIVE_TIME	时间	20240115 06:00:00	
    属性-到达地址	ATTR_ARRIVE_ADDR	字符串	海淀区紫金长安3栋052	
    属性-到达地址类型	ATTR_ARRIVE_ADD_TYPE	字符串	住宅、公司、餐厅、超市、景点、影院、4S店、小学、中学、大学	
    属性-到达地址经度	ATTR_ARRIVE_LONGITUDE	浮点型	116.3108	
    属性-到达地址纬度	ATTR_ARRIVE_LATITUDE	浮点型	39.9072	
    指标-到达地址停留时间（分钟）	METRIC_ARRIVE_STAY_TIME	整型	607	
    属性-到达地址基站	ATTR_ARRIVE_LAC_CODE	字符串	L00014	
    指标-通话时间	METRIC_CALL_TIMESTAMP	时间	20240101 11:48:00	
    属性-通话对端号码	ATTR_CALL_NUMBER	字符串	18517699330	
    属性-通话漫游类型	ATTR_CALL_ROAMING_TYPE	字符串	国内、国际	
    指标-通话时长（分钟）	METRIC_CALL_DURATION_MINUTES	浮点型	2.4	
    指标-通话费用	METRIC_CALL_APP_COST_RMB	浮点型	0	
    指标-APP使用时间	METRIC_TIMESTAMP	时间	2024/1/1 7:00	
    指标-APP上行流量（MB）	METRIC_APP_UPLOAD_TRAFFIC_MB	浮点型	1.34	
    指标-APP下行流量（MB）	METRIC_APP_DOWNLOAD_TRAFFIC_MB	浮点型	2.67	
    指标-APP在线时长（分钟）	METRIC_APP_ONLINE_DURATION_MINUTES	整型	14	
    属性-APP名称	ATTR_APP_USED	字符串	微信、抖音、支付宝、淘宝、拼多多、高德地图、QQ、快手、百度、京东、小红书、微博、美团、滴滴出行、哔哩哔哩、饿了么、WPS Office、腾讯视频、爱奇艺、携程、夸克、百度网盘、Soul、知乎、百度地图、网易新闻、虎牙直播、番茄小说、剪映、懂车帝	
    指标-APP花费（RMB）	ATTR_APP_COST_RMB	浮点型	0	
    属性-集团产品名称	ATTR_GROUP_PRODUCT_NAME	字符串	移动看家, 移动高清, 移动康养, 移动车家, 移动云盘,139邮箱, 云手机, 云电脑, 行车卫士, 集团语音	                
    """

    data_sample = """
    20250101|北京|P39752|13697491495|U22808|C77213|72|女|北京市|密云区|不老屯镇|G00130|39.5|115.94|B06647|40.12|116.63|B00067|39.8|116.69|稳定型|0|0|0|0|0|1|FC00614|GEO00169|SC00885|CYC00362|CPC00886|CTC00470|BC00283|OC00534|274.117615.0|50950.0|201.57|71.66|49.4|16.5|72.02|32.27|1.42|15.61|18.58|48|20190511|和飞享套餐|82|311||陕西省丽娟县魏都向街t座 471483|否|856|615|19|9|河北省邯郸市友好澳门路O座 922221|139邮箱|国家电网公司|579|20240512 04:42:30|广西壮9|127|L00320|20240304 20:20:56|15213535211|国际|16.8|6.46|20240518 11:07:19|7.59|7.6|8|京东|12.24|移动高清
    20250101|北京|P71379|13944849915|U20652|C19173|35|女|北京市|石景山区|广宁街道|G00627|40.87|115.63|B02638|40.14|115.42|B02740|40.24|116.56|波动型|0|1|0|0|1|0|FC00927|GEO00404|SC00965|CYC00235|CPC00145|CTC00799|BC00507|OC00900|4462114.0|63776.0|217.85|43.24|4.52|21.84|68.18|45.18|2.15|39.0|15.33|37|20160121|天翼畅享套餐|9|574||上海市齐齐哈尔县花溪淮安路K座 165899|是|536|517|12|9|上海市北京市秀英孙街C座 181545|云手机|顺丰控股集团|580|20240903 18:02:51|江8|L09841|20241128 04:10:03|13464530864|国内|42.6|6.67|20240901 08:30:04|9.31|29.83|71|爱奇艺|19.85|行车卫士
    20250101|北京|P38664|15612803181|U09527|C89431|43|男|北京市|怀柔区|九渡河镇|G00987|40.43|115.81|B00973|40.83|116.21|B00374|40.93|115.89|稳定型|0|0|0|1|0|1|FC00999|GEO00186|SC00887|CYC00857|CPC00881|CTC00633|BC00337|OC00914|196.62322.0|85831.0|142.28|62.58|39.06|18.59|18.25|33.87|23.94|44.71|13.16|149|20110105|冰淇淋套餐|86|663||辽宁省瑞市怀柔秦路X座 719788|是|606|307|7|2|湖北省东莞县锡山兴安盟路z座 448679|集团语音|顺丰控股集团|665|20240320 02:04:09|甘|574|L07281|20240619 07:35:00|13955600909|国内|49.9|1.46|20241122 17:35:20|5.32|31.76|37|番茄小说|27.21|移动云盘
    """

    sankey_background = """
     用js的echart代码生成桑基图。输入为json字符串，格式为[{"source":..., "target":..., "value":...}, ...]
    
    输入格式说明:
    ============
    
    参数:
    - data: 桑基图数据，JSON字符串格式
    
    数据格式:
    =========
    
    桑基图数据格式 (data):
    [
        {"source": "A", "target": "B", "value": 100},
        {"source": "A", "target": "C", "value": 80},
        {"source": "B", "target": "D", "value": 60},
        {"source": "C", "target": "D", "value": 40}
    ]
    
    字段说明:
    =========
    - source: 源节点名称，字符串类型
    - target: 目标节点名称，字符串类型  
    - value: 流量值，必须是数字类型
    - 请根据用户原始query决定，source和target是什么，例如在query"请分析20250701日用户性别和全球通用户的流转关系"中，source是性别，target是全球通用户
    - 样例一，"请分析20250701日用户性别和全球通用户的流转关系"中，source是性别，target是全球通用户
    - 样例二，"请分析2025-06-01日所属市和用户性别的流转关系"中，source是所属市，target是用户性别
    
    注意事项:
    =========
    1. 输入必须是有效的JSON字符串
    2. value字段必须是数字类型
    3. source和target字段必须是字符串类型
    4. 不要将不同类型的指标组合，source和target里分别是数据库中可查询的单一指标
    
    示例:
    =====
    >>> sankey_data = [
    ...     {"source": "用户注册", "target": "完成验证", "value": 1000},
    ...     {"source": "完成验证", "target": "首次登录", "value": 800},
    ...     {"source": "首次登录", "target": "完成设置", "value": 600},
    ...     {"source": "完成设置", "target": "活跃用户", "value": 400}
    ... ]
    
    """

    sql_generation_template = f"""
    你是一位资深数据库分析师，精通SQL语法和语义解析，擅长将自然语言查询转化为高效准确的SQL语句。请严格遵守数据库安全规范，仅生成SELECT查询语句。 
    请参考用户以自然语言描述的查询需求，数据库结构，样例数据和桑基图数据的结构，将用户的query转化为桑基图数据。

    用户生成桑基图的查询需求是:
    {sql_query}
    
    桑基图数据结构:
    {sankey_background}

    数据库结构：
    {schema_description}

    样例数据:
    {data_sample}


    要求：
    1. 只输出SQL语句，直接输出字符串，不要包含解释，语句前后不要包含"```sql""```"
    2. 使用标准SQL语法
    3. 确保查询效率
    4. 请预先测试，不要有语法错误
    """

    response = await deepseek_response(sql_generation_template)

    return response


async def visualize_sankey_chart(data: str) -> str:
    try:
        # 解析输入数据
        sankey_data = json.loads(data)
        logging.info(f"桑基图数据: {sankey_data}")

        # 验证数据格式
        if not isinstance(sankey_data, list):
            return "输入数据格式错误: 数据必须是数组格式"

        # 提取所有节点名称
        nodes = set()
        for item in sankey_data:
            if not isinstance(item, dict) or 'source' not in item or 'target' not in item or 'value' not in item:
                return "输入数据格式错误: 每个数据项必须包含source、target、value字段"

            if not isinstance(item['value'], (int, float)) or item['value'] <= 0:
                return "输入数据格式错误: value字段必须是正数"

            nodes.add(item['source'])
            nodes.add(item['target'])

        # 构建节点列表
        node_list = [{"name": node} for node in nodes]

        # 构建边列表
        links = []
        for item in sankey_data:
            links.append({
                "source": item['source'],
                "target": item['target'],
                "value": item['value']
            })

        # 生成ECharts配置
        echarts_config = {
            "title": {
                "text": "",
                "textStyle": {
                    "fontSize": 9,
                    "fontWeight": "bold"
                }
            },
            "tooltip": {
                "trigger": "item",
                "triggerOn": "mousemove"
            },
            "series": [
                {
                    "type": "sankey",
                    "layout": "none",
                    "data": node_list,
                    "links": links,
                    "itemStyle": {
                        "borderWidth": 1,
                        "borderColor": "#aaa"
                    },
                    "lineStyle": {
                        "color": "source",
                        "curvature": 0.5
                    },
                    "emphasis": {
                        "focus": "adjacency"
                    }
                }
            ]
        }

        echarts_config = json.dumps(echarts_config, ensure_ascii=False)

        import os
        # output_file = "sankey_chart.html"
        output_file_2 = "chart_option.json"
        # with open(file_folder_path / output_file, "w", encoding="utf-8") as f:
        #     f.write(html_content)

        with open(file_folder_path / output_file_2, "w", encoding="utf-8") as f:
            f.write(echarts_config)

        with open('chart_option.json', "w", encoding="utf-8") as f:
            f.write(echarts_config)

        return echarts_config

    except json.JSONDecodeError as e:
        return f"JSON解析错误: {e}"
    except Exception as e:
        return f"生成桑基图时发生错误: {e}"